package base64;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;

public class Base64Reader extends Reader{

  private final static char[] TO_BASE64 = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
    'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
    '0','1','2','3','4','5','6','7','8','9','+','/'}; 

  private InputStream in;
  
  public static void main(String[] args) throws IOException {
    ByteArrayInputStream in = new ByteArrayInputStream("Franz jagt im komplett verwahrlosten Taxi quer durch Bayern".getBytes(Charset.forName("ascii")));
    Base64Reader reader = new Base64Reader(in);
    StringBuilder sb = new StringBuilder();
    int next = reader.read();
    while(next != -1){
      sb.append((char)next);
      next = reader.read();
    }
    System.out.println(sb.toString());
    System.out.println(toBase64("Franz jagt im komplett verwahrlosten Taxi quer durch Bayern".getBytes(Charset.forName("ascii"))));
  }
  
  public Base64Reader(InputStream in){
    this.in = in;
  }
  
  @Override
  public void close() throws IOException {
    in.close();
  }

  @Override
  public int read(char[] cbuf, int off, int len) throws IOException {
    int pos = off;
    for (int i = 0; i < len; i++){
      int next = readCharacter();
      if (next == -1){
        if (i == 0)
          return -1;
        return i;
      }
      cbuf[pos] = (char)next;
      pos++;
    }
    return len;
  }

  private byte state = 0;
  
  private int memory;
  
  private boolean endReached;
  
  public static String toBase64(byte[] bytes) throws IOException{
    ByteArrayInputStream in = new ByteArrayInputStream(bytes);
    Base64Reader reader = new Base64Reader(in);
    int read;
    final char[] buffer = new char[1024];
    StringBuilder sb = new StringBuilder();
    while ((read = reader.read(buffer, 0, buffer.length)) != -1){
      sb.append(buffer, 0, read);
    }
    reader.close();
    return sb.toString();
  }
  
  private int readCharacter() throws IOException {
    if (endReached){
      if (state == 0) {
        return -1;
      } else {
        state++;
        if (state == 4)
          state = 0;
        return '=';
      }
    }
    
    char result = 0;
    switch (state){
    case 0:
      int next = in.read();
      if (next == -1)
        return -1;
      state = 1;
      result = TO_BASE64[next >> 2];
      memory = (next & 0x03) << 4;
      break;
    case 1:
      state = 2;
      next = in.read();
      if (next == -1){
        endReached = true;
        next = 0;
      }
      result = TO_BASE64[(next >> 4) | memory];
      memory = (next & 0x0f) << 2;
      break;
    case 2:
      state = 3;
      next = in.read();
      if (next == -1){
        next = 0;
        endReached = true;
      }
      result = TO_BASE64[(next >> 6) | memory];
      memory = next & 0x3f;
      break;
    case 3:
      state = 0;
      result = TO_BASE64[memory];
      break;
    }
    return result;
  }
}